﻿using System.Data.Common;
using System.Data.Entity;
using Abp.Zero.EntityFramework;
using Akso.Authorization.Roles;
using Akso.Authorization.Users;
using Akso.Chat;
using Akso.Friendships;
using Akso.MultiTenancy;
using Akso.Storage;
using Abp.Domain.Entities.Auditing;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;
using Akso.AttributeConfig;
using System.Collections.Generic;
using Abp.Domain.Entities;
using Akso.Entities;

namespace Akso.EntityFramework
{
    public class AbpZeroTemplateDbContext : AbpZeroDbContext<Tenant, Role, User>
    {
        /* Define an IDbSet for each entity of the application */
        public virtual IDbSet<ClassroomEntity> Classrooms { get; set; }
        public virtual IDbSet<CodeGroupEntity> CodeGroups { get; set; }
        public virtual IDbSet<CodeEntity> Codes { get; set; }
        //教务管理
        public virtual IDbSet<CourseEntity> Courses { get; set; }
        public virtual IDbSet<CourseScheduleEntity> CourseSchedules { get; set; }

        public virtual IDbSet<BinaryObject> BinaryObjects { get; set; }

        public virtual IDbSet<Friendship> Friendships { get; set; }

        public virtual IDbSet<ChatMessage> ChatMessages { get; set; }

        /* Setting "Default" to base class helps us when working migration commands on Package Manager Console.
         * But it may cause problems when working Migrate.exe of EF. ABP works either way.         * 
         */
        public AbpZeroTemplateDbContext()
            : base("Default")
        {
            PrintSQLForDebug();
        }

        /* This constructor is used by ABP to pass connection string defined in AbpZeroTemplateDataModule.PreInitialize.
         * Notice that, actually you will not directly create an instance of AbpZeroTemplateDbContext since ABP automatically handles it.
         */
        public AbpZeroTemplateDbContext(string nameOrConnectionString)
            : base(nameOrConnectionString)
        {
            PrintSQLForDebug();
        }

        /* This constructor is used in tests to pass a fake/mock connection.
         */
        public AbpZeroTemplateDbContext(DbConnection dbConnection)
            : base(dbConnection, true)
        {
            PrintSQLForDebug();
        }

        #region Added by Hollis Yao
        /// <summary>
        /// 在调试状态下，把SQL输出到VS的Output窗口。
        /// </summary>
        private void PrintSQLForDebug()
        {
#if DEBUG
            Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
#endif
        }

        /// <summary>
        /// 控制生成数据库代码。
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            //增加时间和浮点数的精度控制。
            modelBuilder.Conventions.Add(new DateTimePrecisionAttributeConvention());
            modelBuilder.Conventions.Add(new DecimalPrecisionAttributeConvention());
        }

        //在更新记录期间，禁止对前三个字段的无用的更新操作。
        private static readonly HashSet<string> IgnoreCheckUpdateFields = new HashSet<string>
        {
            nameof(IAudited.CreationTime),
            nameof(IAudited.LastModificationTime),
            nameof(IAudited.LastModifierUserId),
            nameof(Entity.Id),
        };

        private void BlockNeedLessUpdate()
        {
            //LEVEL 1: always CreationTime
            var allModified = ChangeTracker
                .Entries<ICreationAudited>()
                .Where(p => p.State == EntityState.Modified);

            foreach (var item in allModified)
                item.Property(nameof(ICreationAudited.CreationTime))
                    .IsModified = false;

            //LEVEL 2: only LastModificationTime LastModifierUserId
            var allModified2 = ChangeTracker
                .Entries<IAudited>()
                .Where(p => p.State == EntityState.Modified);

            foreach (var item in allModified2)
            {
                var changed = item.CurrentValues.PropertyNames
                 .Any(p => !IgnoreCheckUpdateFields.Contains(p)
                 && false == item.CurrentValues[p]?.Equals(item.OriginalValues[p]));

                if (changed) continue;
                item.Property(nameof(IAudited.LastModificationTime)).IsModified = false;
                item.Property(nameof(IAudited.LastModifierUserId)).IsModified = false;
            }
        }

        public override int SaveChanges()
        {
            BlockNeedLessUpdate();
            return base.SaveChanges();
        }
        public override Task<int> SaveChangesAsync(CancellationToken ct)
        {
            BlockNeedLessUpdate();
            return base.SaveChangesAsync(ct);
        }
        #endregion

    }
}
